import os, sys
import re

from twisted.internet import protocol, reactor
from twisted.words.protocols import irc
from twisted.protocols.basic import LineReceiver


os.chdir("..")
sys.path.insert(0, ".")
sys.path.insert(0, "..")
os.environ["DJANGO_SETTINGS_MODULE"] = "pygameweb.settings"

from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
from news.models import News
from cab.models import Snippet
from projects.models import Project, Release, Poll
from utils.redirects import get_tiny_url

bot_settings = {
    "nick": getattr(settings, "BOT_NICK", "b0t"),
    "ip": getattr(settings, "BOT_IP", "irc.freenode.net"), 
    "port": getattr(settings, "BOT_PORT", 6667),
    "channels": getattr(settings, "BOT_CHANNELS", ("bottest",)),
    "welcome": getattr(settings, "BOT_ENTER_MSG", ""),
    }


###################
## bot functions ##
###################

def help(args, cmds):
    "get a list of all commands or help to one command"
    if args and args[0] in cmds:
        return cmds[args[0]].__doc__.replace("the site", str(Site.objects.get_current()))
    else:
        return "commands: "+", ".join(sorted(cmds.iterkeys()))
                  

# new doc version by HydroKirby
# A class always has its first letter capitalized; modules are lowercase.

# Deepness examples:
# pygame.Overlay            pygame.Class                 overlay.html#pygame.Overlay
# pygame.Overlay.display    pygame.Class.method          overlay.html#Overlay.display
# pygame.cursors            pygame.module                cursors.html#pygame.cursors
# pygame.cursors.compile    pygame.module.method         cursors.html#pygame.cursors.compile
# pygame.font               pygame.module                font.html#pygame.font
# pygame.font.get_fonts     pygame.module.method         font.html#pygame.font.get_fonts
# pygame.font.Font          pygame.module.Class          font.html#pygame.font.Font
# pygame.font.Font.get_bold pygame.module.Class.method   font.html#Font.get_bold
# pygame.mixer.music.load   pygame.module.module.method  music.html#pygame.mixer.music.set_volume

# These should be classes with methods - not like pygame.font.SysFont which has no methods.
classes = {
    "CD":"cdrom",
    "Color":"color",
    "Font":"font",
    "Joystick":"joystick",
    "Mask":"mask",
    "Channel":"mixer",
    "Sound":"mixer",
    "Movie":"movie",
    "Overlay":"overlay",
    "PixelArray":"pixelarray",
    "Rect":"rect",
    "Group":"sprite",
    "LayeredDirty":"sprite",
    "LayeredUpdates":"sprite",
    "RenderUpdates":"sprite",
    "Sprite":"sprite",
    "Surface":"surface",
    "Clock":"time"}
specialCaseClasses = {
    "CD":"CD",
    "DIRTYSPRITE":"DirtySprite",
    "GROUPSINGLE":"GroupSingle",
    "LAYEREDDIRTY":"LayeredDirty",
    "LAYEREDUPDATES":"LayeredUpdates",
    "ORDEREDUPDATES":"OrderedUpdates",
    "PIXELARRAY":"PixelArray",
    "RENDERUPDATES":"RenderUpdates",
    "SYSFONT":"SysFont"}
pygameMethods = ["init", "quit", "register_quit", "version",
    "get_sdl_version", "get_sdl_byteorder", "error", "get_error"]

def doc(args):
    "get the url to a pygame function or class documentation"
    direct = {"": "http://pygame.org/docs/",
          "ref": "http://pygame.org/docs/ref/"}
    if not args:
        arg = ""
    else:
        arg = args[0].strip()
    if arg in direct:
        return direct[arg]
    else:
        if arg.lower().startswith("pygame."):
            arg = arg[7:]
        if not "." in arg:
            arg += "."
        
        module, rest = arg.split(".", 1)
        # Note: A class on the very end of the url without a method
        # still uses the module.html#pygame.module.Class format!
        # special case: mixer.music*
        if rest.lower().startswith('music'):
            return "http://pygame.org/docs/ref/music.html#pygame.mixer.music.%s" % rest[6:]
        if not module:
            # All the user wanted was pygame and we ate it.
            return "http://pygame.org/docs/ref/pygame.html"
        elif module.lower() in pygameMethods:
            return "http://pygame.org/docs/ref/pygame.html#pygame.%s" % \
                module.lower()
        elif module.upper() in specialCaseClasses:
            classArg = specialCaseClasses[module.upper()]
        else:
            classArg = module.capitalize()
        # The 2nd statement checks if the module has the same name
        # as the class name (eg font and Font). At that point, the
        # condition is case-sensitive of the 1st letter (eg using
        # Font makes the whole condition True, but font makes
        # it all False).
        if classArg in classes and rest != "" and \
            (classArg.lower() != classes[classArg].lower() or \
            module[0].isupper()):
                return "http://pygame.org/docs/ref/%s.html#%s.%s" % \
                (classes[classArg].lower(), classArg,
                rest.lower())
        url = "http://pygame.org/docs/ref/%s.html" % \
            module.lower()

        if rest:
            if rest.upper() in specialCaseClasses:
                rest = specialCaseClasses[rest.upper()]
            # The special case for rect allows draw.rect.
            elif rest.capitalize() in classes and rest != 'rect':
                # The capitalization allows font.Font and such.
                rest = rest.capitalize()
            else:
                rest = rest.lower()
            if not "." in rest:
                anch = "pygame."+module.lower()+"."+rest
            else:
                anch = rest
            url += "#"+anch
        return url


def _last_objects(cls, args):
    if args:
        try:
            # 1 <= n <= 5
            n = min(max(int(args[0]), 1), 5)
        except ValueError:
            n = 1
    else:
        n = 1
    
    obj_list = cls.objects.all()[:n]
    retvals = []
    for obj in obj_list:
        retvals.append("%s: http://%s%s" % (obj, 
                                         Site.objects.get_current(),
                                         get_tiny_url(obj)))
    return retvals

def last_news(args):
    "get last n=1 news from the site"
    return _last_objects(News, args)
    

def last_releases(args):
    "get recent n=1 releases from the site"
    return _last_objects(Release, args)

def last_snippets(args):
    "get recent n=1 snippets from the site"
    return _last_objects(Snippet, args)

def giyf(args):
    "get a url to show someone how to use google"
    q = args and "?q="+"+".join(args) or ""
    return "http://lmgtfy.com/%s" % q

def use_paste(args):
    "say a user to use a paste service"
    t = "lease use a paste service to show \
others your code instead of copying it to the channel directly. \
For example, go to http://paste.pocoo.org/ , paste your code and \
tell us just the url of that paste."""
    if args:
        return ", ".join(args) + ", p" + t
    else:
        return "P" + t

def paste_url(args):
    "link to paste service for forgetful people"
    return "Paste your code: http://paste.pocoo.org/+python"

def about(args):
    "about the authors"
    return "by schlangen aka jug, doc command by HydroKirby"

def whois(args):
    "check if a user registered on the site"
    if not args:
        return "who is who?"
    else:
        u = User.objects.filter(profile__irc_nick=args[0])
        if not u:
            return "%s is not know at %s"%(args[0], Site.objects.get_current())
        elif len(u) > 1:
            return "Err...found more than one user with irc nick '%s'." % (args[0])
        else:
            u = u[0]
            return str("%s is known as %s: http://%s%s"% (args[0],
                                                u.get_profile().display_name,
                                                Site.objects.get_current(), 
                                                get_tiny_url(u.get_profile())))

            
######################
            

class Bot(irc.IRCClient):

    nickname = bot_settings["nick"]
    realname = "pygweb bot"
    topics = {}

    def connectionMade(self):
        irc.IRCClient.connectionMade(self)
        InternalInterface.bot = self
        self.channels = []

    def joined(self, channel):
        self.msg(channel, bot_settings["welcome"])
        self.channels.append(channel)
        
    def signedOn(self):
        for chan in bot_settings["channels"]:
            self.join("#%s" % chan)

    def privmsg(self, user, channel, msg):
        """This will get called when the bot receives a message."""
        print "privmsg", user, channel
        print msg
        print
        user = user.split('!', 1)[0]        
        # Check to see if they're sending me a private message
        # is that bad? users can test a command that way
        if channel == self.nickname and not self.nickname in msg:
             msg = "%s: %s" % (self.nickname, msg)
             channel = user
        #    msg = "It isn't nice to whisper!  Play nice with the group."
        #    self.msg(user, msg)
        #    print "PRIVCHAT:", user, msg
        #    return

        # Otherwise check to see if it is a message directed at me
        if self.nickname in msg:
            print "CMD:", user, msg
            nick, msg = msg.strip().split(" ", 1)
            if not self.nickname in nick:
                return
            cmd = msg.strip()
            args = []
            if " " in cmd:
               cmd, args = cmd.split(" ", 1)
               args = args.split()
               if cmd.strip() == ":" and len(args) > 1:
                   cmd, args = args[0], args[1:]


            cmds = {"about": about, 
                    "help": help,
                    "doc": doc,
                    "news": last_news,
                    "releases": last_releases,
                    "snippets": last_snippets,
                    "giyf": giyf,
                    "use_paste": use_paste,
                    "paste_url": paste_url,
                    "whois": whois}
            aliases = {"jfgi": "giyf",}
            if cmd in aliases:
                cmd = aliases[cmd]
            if cmd in cmds:
                cmd = cmds[cmd]
                if cmd == help:
                    retmsg = cmd(args, cmds)
                else:
                    retmsg = cmd(args)
            elif channel == user and cmd == "bct":
                self.broadcast(" ".join(args))
                retmsg = "message was send to %s" % ", ".join(self.channels)
            else:
                retmsg = "err...what do you mean? try help"
            if type(retmsg) == list:
                for line in retmsg:
                    self.msg(channel, line.encode("utf-8"))
            else:
                self.msg(channel, retmsg.encode("utf-8"))
                              
            
            
    def broadcast(self, msg):
        for chan in self.channels:
            self.msg(chan, msg)
            
            
    def topicUpdated(self, user, channel, newTopic):          
        #print "got topic of ", channel, ":", newTopic, "by", user  
        self.topics[channel] = newTopic

    def set_title(self):
        """
        update news in channel titles (topics)
        """
        n = News.objects.latest()
        current_site = Site.objects.get_current()
        news = "%s: http://%s%s" % (n.title, current_site, get_tiny_url(n))
        for channel in self.channels:
            if channel in self.topics:
                old_topic = self.topics[channel]
                new_topic = re.sub(settings.BOT_TITLE_REGEX,
                                   "\\1%s\\2" % news,
                                   old_topic)
                if new_topic != old_topic:
                    self.topic(channel, new_topic.encode("utf-8"))
            else:
                print "don't know topic of ", channel

class BotFactory(protocol.ClientFactory):

  protocol = Bot

  def clientConnectionLost(self,connector,reason):
    connector.connect()

  def clientConnectFailed(self,connector,reason):
    reactor.stop()
    
class InternalInterface(LineReceiver):
    bot = None
    
    def connectionMade(self):
        self.buffer = []
        self.delimiter = "\n"
        self.sendLine("pywbotinterface:ready")
                
    def lineReceived(self, line):
        self.buffer.append(line)
        if self.bot and self.buffer:
            for line in self.buffer:
                if "::settitle::" in line: #! test
                    self.bot.set_title()
                else:
                    self.bot.broadcast(line)
            self.buffer = []
            

def start():
   f = BotFactory()
   reactor.connectTCP(bot_settings["ip"], bot_settings["port"], f)
   f = protocol.Factory()
   f.protocol = InternalInterface
   reactor.listenTCP(7060, f, interface = '127.0.0.1')
   reactor.run()
